package com.inspectortime.domain.pricing;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.inspectortime.common.PersistentEntityBase;
import com.inspectortime.domain.Inspection;
import com.inspectortime.domain.InspectorCompany;
import com.inspectortime.domain.impl.InspectorCompanyImpl;
import com.inspectortime.domain.type.FoundationType;
import com.inspectortime.domain.type.PropertyType;
import com.inspectortime.service.ZipCodeSearchService;
import com.inspectortime.timestamp.Timestamped;

@Entity
@Table(name = "BASE_FEE_WITH_ADJUSTERS_CALCULATOR")
public class BaseFeeWithAdjustersCalculator extends PersistentEntityBase
		implements FeeCalculator, Timestamped {

	public static final int DEFAULT_BATHROOMS_INCREMENT = 1;
	public static final int DEFAULT_BEDROOMS_INCREMENT = 1;
	public static final int DEFAULT_LISTED_PRICE_INCREMENT = 10000;

	public static final int DEFAULT_SQFT_INCREMENT = 250;

	private static Log log = LogFactory
			.getLog(BaseFeeWithAdjustersCalculator.class);

	public static final float MAX_BATHROOMS = 6;

	public static final int MAX_BEDROOMS = 7;

	public static final float MAX_LISTED_PRICE = 1500000;
	public static final double MAX_SQFT = 6000;
	private double baseFee;

	private Date createdTime;
	private String distanceInMilesCenterZip;
	
	private double distanceInMilesFeeIncreasePerMile;

	private int distanceInMilesLimit;

	private int dollarsToRoundFeeAdjustmentTo;

	private FoundationType foundationType = FoundationType.Unspecified;

	private InspectorCompany inspectorCompany;

	private Date lastModifiedTime;

	private double listedPriceInThousandsIncreaseAmount;

	private double listedPriceInThousandsIncreaseLimit;
	private float maxListedPrice;
	
	private int maxNumberOfAcUnits;

	private float maxNumberOfBathrooms;

	private int maxNumberOfBedrooms;
	private int maxNumberOfStories;
	private double maxSquareFeet;

	private float minListedPrice;

	private int minNumberOfAcUnits;

	private float minNumberOfBathrooms;

	private int minNumberOfBedrooms;

	private int minNumberOfStories;

	private double minSquareFeet;

	private boolean occupied;

	private double perBathroomIncreaseAmount;

	private double perBathroomIncreaseLimit;

	private double perBedroomIncreaseAmount;

	private int perBedroomIncreaseLimit;

	private PropertyType propertyType = PropertyType.Unspecified;

	private int sequentialId;

	private String source;
	private double squareFeetInHundredsIncreaseAmount;
	private double squareFeetInHundredsIncreaseLimit;
	private String supportedZipCodes;

	private ZipCodeSearchService zipCodeSearchService;

	public double calculateFee(Inspection inspection) {

		// Base
		double fee = baseFee;

		// Beds
		if (this.getPerBedroomIncreaseLimit() > 0
				&& inspection.getProperty().getNumberOfBedrooms() > this
						.getPerBedroomIncreaseLimit()) {
			fee += (this.getPerBedroomIncreaseAmount() * (inspection
					.getProperty().getNumberOfBedrooms() - this
					.getPerBedroomIncreaseLimit()));
		}

		// Baths
		if (this.getPerBathroomIncreaseLimit() > 0
				&& inspection.getProperty().getNumberOfBathrooms() > this
						.getPerBathroomIncreaseLimit()) {
			fee += (this.getPerBathroomIncreaseAmount() * (inspection
					.getProperty().getNumberOfBathrooms() - this
					.getPerBathroomIncreaseLimit()));
		}

		// Listing price
		if (this.getListedPriceInThousandsIncreaseLimit() > 0
				&& inspection.getProperty().getListedPrice() > (this
						.getListedPriceInThousandsIncreaseLimit()*1000)) {
			double increaseAmountPerThousand = this.getListedPriceInThousandsIncreaseAmount();
			double priceOverLimitInThousands = (inspection.getProperty().getListedPrice()/1000) - this.getListedPriceInThousandsIncreaseLimit();
			double calculatedIncrease = priceOverLimitInThousands * increaseAmountPerThousand;
			fee += calculatedIncrease;
		}

		// Sq ft
		if (this.getSquareFeetInHundredsIncreaseLimit() > 0
				&& inspection.getProperty().getSquareFeet() > this
						.getSquareFeetInHundredsIncreaseLimit()) {
			fee += (this.getSquareFeetInHundredsIncreaseAmount() * ((inspection
					.getProperty().getSquareFeet() - this
					.getSquareFeetInHundredsIncreaseLimit()) / 100));
		}

		// Distance
		if (this.getDistanceInMilesLimit() > 0
				&& this.getDistanceInMilesCenterZip() != null
				&& this.getDistanceInMilesCenterZip().length() == 5) {
			double dist = zipCodeSearchService.findDistanceInMiles(this
					.getDistanceInMilesCenterZip(), inspection.getProperty()
					.getLocation().getZipCode());
			double distOverLimit = dist - this.getDistanceInMilesLimit();
			if (distOverLimit > 0) {
				fee += distOverLimit * this.getDistanceInMilesFeeIncreasePerMile();	
			}	
		}

		// Round final fee amount
		if (this.getDollarsToRoundFeeAdjustmentTo() > 0) {
			double feeDivided = fee / this.getDollarsToRoundFeeAdjustmentTo();
			long roundedFeeDivided = Math.round(feeDivided);
			fee = roundedFeeDivided * getDollarsToRoundFeeAdjustmentTo();
		}

		return fee;
	}
	
	public double getBaseFee() {
		return baseFee;
	}	
	
	public Date getCreatedTime() {
		return createdTime;
	}

	public String getDistanceInMilesCenterZip() {
		return distanceInMilesCenterZip;
	}	
	
	public double getDistanceInMilesFeeIncreasePerMile() {
		return distanceInMilesFeeIncreasePerMile;
	}
	
	public int getDistanceInMilesLimit() {
		return distanceInMilesLimit;
	}	
	
	public int getDollarsToRoundFeeAdjustmentTo() {
		return dollarsToRoundFeeAdjustmentTo;
	}

	public FoundationType getFoundationType() {
		return foundationType;
	}

	//@JoinColumn(name="inspectorCompany_id", insertable=false, updatable=false)
	@ManyToOne(targetEntity = InspectorCompanyImpl.class, cascade = { CascadeType.ALL })
	@JoinColumn(name="inspectorCompany_id")
	public InspectorCompany getInspectorCompany() {
		return inspectorCompany;
	}

	public Date getLastModifiedTime() {
		return lastModifiedTime;
	}

	public double getListedPriceInThousandsIncreaseAmount() {
		return listedPriceInThousandsIncreaseAmount;
	}

	public double getListedPriceInThousandsIncreaseLimit() {
		return listedPriceInThousandsIncreaseLimit;
	}

	public float getMaxListedPrice() {
		return maxListedPrice;
	}

	public int getMaxNumberOfAcUnits() {
		return maxNumberOfAcUnits;
	}

	public float getMaxNumberOfBathrooms() {
		return maxNumberOfBathrooms;
	}

	public int getMaxNumberOfBedrooms() {
		return maxNumberOfBedrooms;
	}

	public int getMaxNumberOfStories() {
		return maxNumberOfStories;
	}

	public double getMaxSquareFeet() {
		return maxSquareFeet;
	}

	public float getMinListedPrice() {
		return minListedPrice;
	}

	public int getMinNumberOfAcUnits() {
		return minNumberOfAcUnits;
	}

	public float getMinNumberOfBathrooms() {
		return minNumberOfBathrooms;
	}

	public int getMinNumberOfBedrooms() {
		return minNumberOfBedrooms;
	}

	public int getMinNumberOfStories() {
		return minNumberOfStories;
	}

	public double getMinSquareFeet() {
		return minSquareFeet;
	}

	public double getPerBathroomIncreaseAmount() {
		return perBathroomIncreaseAmount;
	}

	public double getPerBathroomIncreaseLimit() {
		return perBathroomIncreaseLimit;
	}

	public double getPerBedroomIncreaseAmount() {
		return perBedroomIncreaseAmount;
	}

	public int getPerBedroomIncreaseLimit() {
		return perBedroomIncreaseLimit;
	}

	public PropertyType getPropertyType() {
		return propertyType;
	}

	public int getSequentialId() {
		return sequentialId;
	}

	public String getSource() {
		return source;
	}

	public double getSquareFeetInHundredsIncreaseAmount() {
		return squareFeetInHundredsIncreaseAmount;
	}

	public double getSquareFeetInHundredsIncreaseLimit() {
		return squareFeetInHundredsIncreaseLimit;
	}

	@Transient
	public String[] getSupportedZipCodeArray() {
		if (!StringUtils.hasText(this.getSupportedZipCodes())) {
			return new String[0];
		}
		String[] results = this.getSupportedZipCodes().split("\\,");
		String[] trimmedResults = new String[results.length];
		for (int i = 0; i < results.length; i++) {
			trimmedResults[i] = results[i].trim();
		}
		return trimmedResults;
	}

	@Transient
	public List<String> getSupportedZipCodeList() {
		String[] array = getSupportedZipCodeArray();
		return (List<String>) CollectionUtils.arrayToList(array);
	}

	public String getSupportedZipCodes() {
		return supportedZipCodes;
	}

	public boolean hasSupportedZipCodes() {
		return StringUtils.hasText(this.supportedZipCodes);
	}

	public boolean isSupportedZip(String zipCode) {

		// Count the absence of a zip code in the calculator as "supports all"
		if (!StringUtils.hasText(this.getSupportedZipCodes())) {
			return true;
		}

		boolean match = false;
		String[] zips = getSupportedZipCodeArray();
		for (int i = 0; i < zips.length; i++) {
			if (zips[i].equals(zipCode.trim())) {
				match = true;
			}
		}
		return match;
	}

	/**
	 * Are any of these zip codes supported by this calculator?
	 * @param zipsToCheckAgainst
	 * @return
	 */
	public boolean isSupportedZip(String[] zipsToCheckAgainst) {
		String[] zips = getSupportedZipCodeArray();
		for (int i = 0; i < zips.length; i++) {
			for (int j = 0; j < zipsToCheckAgainst.length; j++) {
				if (zips[i].equals(zipsToCheckAgainst[j].trim())) {
					return true;
				}	
			}			
		}
		return false;
	}

	/**
	 * Finds other fee calculators for the company that this calculator belongs
	 * to
	 * 
	 * @return
	 */
	public List<FeeCalculator> otherFeeCalculatorsForCompany() {
		List<FeeCalculator> filteredList = new ArrayList<FeeCalculator>();
		for (FeeCalculator calculator : this.getInspectorCompany()
				.getFeeCalculators()) {

			// Skip same calculator
			if (this.getSequentialId() != calculator.getSequentialId()) {
				filteredList.add(calculator);
			}
		}
		return filteredList;
	}

	public void setBaseFee(double fixedFee) {
		this.baseFee = fixedFee;
	}
	
	public void setCreatedTime(Date createdTime) {
		this.createdTime = createdTime;
	}

	public void setDistanceInMilesCenterZip(String distanceInMilesCenterZip) {
		this.distanceInMilesCenterZip = distanceInMilesCenterZip;
	}

	public void setDistanceInMilesFeeIncreasePerMile(
			double distanceInMilesFeeIncreasePerMile) {
		this.distanceInMilesFeeIncreasePerMile = distanceInMilesFeeIncreasePerMile;
	}

	public void setDistanceInMilesLimit(int distanceInMilesLimit) {
		this.distanceInMilesLimit = distanceInMilesLimit;
	}

	public void setDollarsToRoundFeeAdjustmentTo(
			int dollarsToRoundFeeAdjustmentTo) {
		this.dollarsToRoundFeeAdjustmentTo = dollarsToRoundFeeAdjustmentTo;
	}

	public void setFoundationType(FoundationType foundationType) {
		this.foundationType = foundationType;
	}

	public void setInspectorCompany(InspectorCompany inspectorCompany) {
		this.inspectorCompany = inspectorCompany;
	}

	public void setLastModifiedTime(Date lastModifiedTime) {
		this.lastModifiedTime = lastModifiedTime;
	}

	public void setListedPriceInThousandsIncreaseAmount(
			double listedPriceInThousandsIncreaseAmount) {
		this.listedPriceInThousandsIncreaseAmount = listedPriceInThousandsIncreaseAmount;
	}

	public void setListedPriceInThousandsIncreaseLimit(
			double listedPriceInThousandsIncreaseLimit) {
		this.listedPriceInThousandsIncreaseLimit = listedPriceInThousandsIncreaseLimit;
	}

	public void setMaxListedPrice(float maxListedPrice) {
		this.maxListedPrice = maxListedPrice;
	}

	public void setMaxNumberOfAcUnits(int maxNumberOfAcUnits) {
		this.maxNumberOfAcUnits = maxNumberOfAcUnits;
	}

	public void setMaxNumberOfBathrooms(float maxNumberOfBathrooms) {
		this.maxNumberOfBathrooms = maxNumberOfBathrooms;
	}

	public void setMaxNumberOfBedrooms(int maxNumberOfBedrooms) {
		this.maxNumberOfBedrooms = maxNumberOfBedrooms;
	}

	public void setMaxNumberOfStories(int maxNumberOfStories) {
		this.maxNumberOfStories = maxNumberOfStories;
	}

	public void setMaxSquareFeet(double maxSquareFeet) {
		this.maxSquareFeet = maxSquareFeet;
	}

	public void setMinListedPrice(float minListedPrice) {
		this.minListedPrice = minListedPrice;
	}

	public void setMinNumberOfAcUnits(int minNumberOfAcUnits) {
		this.minNumberOfAcUnits = minNumberOfAcUnits;
	}

	public void setMinNumberOfBathrooms(float minNumberOfBathrooms) {
		this.minNumberOfBathrooms = minNumberOfBathrooms;
	}

	public void setMinNumberOfBedrooms(int minNumberOfBedrooms) {
		this.minNumberOfBedrooms = minNumberOfBedrooms;
	}

	public void setMinNumberOfStories(int minNumberOfStories) {
		this.minNumberOfStories = minNumberOfStories;
	}

	public void setMinSquareFeet(double minSquareFeet) {
		this.minSquareFeet = minSquareFeet;
	}

	public void setPerBathroomIncreaseAmount(double perBathroomIncreaseAmount) {
		this.perBathroomIncreaseAmount = perBathroomIncreaseAmount;
	}

	public void setPerBathroomIncreaseLimit(double perBathroomIncreaseLimit) {
		this.perBathroomIncreaseLimit = perBathroomIncreaseLimit;
	}

	public void setPerBedroomIncreaseAmount(double perBedroomIncreaseAmount) {
		this.perBedroomIncreaseAmount = perBedroomIncreaseAmount;
	}

	public void setPerBedroomIncreaseLimit(int perBedroomIncreaseLimit) {
		this.perBedroomIncreaseLimit = perBedroomIncreaseLimit;
	}

	public void setPropertyType(PropertyType propertyType) {
		this.propertyType = propertyType;
	}

	public void setSequentialId(int sequentialId) {
		this.sequentialId = sequentialId;
	}

	public void setSource(String source) {
		this.source = source;
	}

	public void setSquareFeetInHundredsIncreaseAmount(
			double squareFeetInHundredsIncreaseAmount) {
		this.squareFeetInHundredsIncreaseAmount = squareFeetInHundredsIncreaseAmount;
	}

	public void setSquareFeetInHundredsIncreaseLimit(
			double squareFeetInHundredsIncreaseLimit) {
		this.squareFeetInHundredsIncreaseLimit = squareFeetInHundredsIncreaseLimit;
	}

	public void setSupportedZipCodes(String supportedZipCodes) {
		this.supportedZipCodes = supportedZipCodes;
	}

	public void setZipCodeSearchService(ZipCodeSearchService service) {
		this.zipCodeSearchService = service;
	}

	public boolean supports(Inspection inspection) {

		if (!isSupportedZip(inspection.getProperty().getLocation().getZipCode())) {
			return false;
		}

		if (this.propertyType != PropertyType.Unspecified
				&& this.propertyType != inspection.getProperty()
						.getPropertyType()) {
			return false;
		}

		if (this.foundationType != FoundationType.Unspecified
				&& this.foundationType != inspection.getProperty()
						.getFoundationType()) {
			return false;
		}

		if (this.minSquareFeet > 0
				&& inspection.getProperty().getSquareFeet() < this.minSquareFeet) {
			return false;
		}
		if (this.maxSquareFeet > 0
				&& inspection.getProperty().getSquareFeet() > this.maxSquareFeet) {
			return false;
		}

		if (this.minListedPrice > 0
				&& inspection.getProperty().getListedPrice() < this.minListedPrice) {
			return false;
		}
		if (this.maxListedPrice > 0
				&& inspection.getProperty().getListedPrice() > this.maxListedPrice) {
			return false;
		}

		if (this.minNumberOfAcUnits > 0
				&& inspection.getProperty().getNumberOfAcUnits() < this.minNumberOfAcUnits) {
			return false;
		}
		if (this.maxNumberOfAcUnits > 0
				&& inspection.getProperty().getNumberOfAcUnits() > this.maxNumberOfAcUnits) {
			return false;
		}

		if (this.minNumberOfBathrooms > 0
				&& inspection.getProperty().getNumberOfBathrooms() < this.minNumberOfBathrooms) {
			return false;
		}
		if (this.maxNumberOfBathrooms > 0
				&& inspection.getProperty().getNumberOfBathrooms() > this.maxNumberOfBathrooms) {
			return false;
		}

		if (this.minNumberOfBedrooms > 0
				&& inspection.getProperty().getNumberOfBedrooms() < this.minNumberOfBedrooms) {
			return false;
		}
		if (this.maxNumberOfBedrooms > 0
				&& inspection.getProperty().getNumberOfBedrooms() > this.maxNumberOfBedrooms) {
			return false;
		}

		if (this.minNumberOfStories > 0
				&& inspection.getProperty().getNumberOfStories() < this.minNumberOfStories) {
			return false;
		}
		if (this.maxNumberOfStories > 0
				&& inspection.getProperty().getNumberOfStories() > this.maxNumberOfStories) {
			return false;
		}

		return true;
	}

}
